//go:build ignore

#include "vmlinux.h"
#include "bpf_helpers.h"
#include "bpf_tracing.h"

struct user_pt_regs {
	__u64 regs[31];
	__u64 sp;
	__u64 pc;
	__u64 pstate;
};

char LICENSE[] SEC("license") = "Dual BSD/GPL";

#define HOST_NS 4026531841
#define MAX_PATH 256
#define MAX_ENTRIES 32
#define MAX_NAME 32
#define EVENT_OPEN_FILE 0
#define EVENT_OPEN_PARENT 1

struct {
	__uint(type, BPF_MAP_TYPE_RINGBUF);
	__uint(max_entries, 1 << 24);
} events SEC(".maps");

struct {
	__uint(type, BPF_MAP_TYPE_HASH);
	__uint(max_entries, 1024);
	__type(key, __u64);
	__type(value, __u64);
} mntns_map SEC(".maps");

struct event {
	u64 timestamp;
	u32 event_type;
	u32 pid;
	u64 mntns;
	u64 magic;
	u64 size;
	u8 cid[MAX_NAME];
	u8 comm[MAX_NAME];
	u8 path[MAX_PATH];
};
struct event *unused __attribute__((unused));

static inline int has_prefix(u8 s[], char prefix[], int prefix_size) {
	for (int i = 0; i < prefix_size; ++i) {
		if (s[i] != prefix[i]) {
			return 0;
		}
	}
	return 1;
}

static inline void submit_event(struct pt_regs *ctx, struct event *data, struct dentry *dentry, int et) {
	data->event_type = et;
	struct qstr q = {};
	bpf_probe_read(&q, sizeof(q), &dentry->d_name);
	bpf_probe_read_str(data->path, MAX_PATH, q.name);

	struct event *event = bpf_ringbuf_reserve(&events, sizeof(struct event), 0);
	if (event != NULL) {
		bpf_probe_read(event, sizeof(struct event), data);
		bpf_ringbuf_submit(event, 0);
	}
}

// return 1 if data is in container ns.
static bool init_data(struct pt_regs *ctx, struct event *data) {
	struct task_struct *task = (struct task_struct *)bpf_get_current_task();
	void *p1, *p2;

	// init ts, pid, comm
	data->timestamp = bpf_ktime_get_ns();
	data->pid = bpf_get_current_pid_tgid() >> 32;
	bpf_get_current_comm(&data->comm, sizeof(data->comm));

	// init cid
	struct css_set *css = task->cgroups;
	bpf_probe_read(&p1, sizeof(void *), &task->cgroups);
	bpf_probe_read(&p2, sizeof(void *), &((struct css_set *)p1)->subsys[0]);
	bpf_probe_read(&p1, sizeof(void *), &((struct cgroup_subsys_state *)p2)->cgroup);
	bpf_probe_read(&p2, sizeof(void *), &((struct cgroup *)p1)->kn);
	if(p2 != NULL) {
		bpf_probe_read(&p1, sizeof(void *), &((struct kernfs_node *)p2)->name);
		bpf_probe_read_str(data->cid, MAX_NAME, p1);
		if (has_prefix(data->cid, "docker-", 7))
			bpf_probe_read_str(data->cid, MAX_NAME, &data->cid[7]);
	}

	// init mntns
	struct ns_common ns;
	bpf_probe_read(&p1, sizeof(void *), &task->nsproxy);
	bpf_probe_read(&p2, sizeof(void *), &((struct nsproxy *)p1)->mnt_ns);
	bpf_probe_read(&ns, sizeof(struct ns_common), &((struct mnt_namespace *)p2)->ns);
	data->mntns = (u64)ns.inum;

	return bpf_map_lookup_elem(&mntns_map, &data->mntns) != NULL;
}

static void report_parent(struct pt_regs *ctx, struct event *data, struct dentry *dentry) {
	for (int i = 1; i < MAX_ENTRIES; i++) {
		struct dentry *last = dentry;
		bpf_probe_read(&dentry, sizeof(dentry), &dentry->d_parent);
		if (dentry == last) {
			return;
		}
		submit_event(ctx, data, dentry, EVENT_OPEN_PARENT);
	}
}

SEC("kprobe/copy_process")
int report_ns(struct pt_regs *ctx) {
	struct event data = {};
	if (init_data(ctx, &data) ||                 // mntns already in map.
		(HOST_NS == data.mntns) ||           // host mntns.
		!has_prefix(data.comm, "runc", 4)) { // not by runc.
		return 0;
	}
	bpf_map_update_elem(&mntns_map, &data.mntns, &data.mntns, BPF_ANY);
	return 0;
}


SEC("kretprobe/do_filp_open")
int report_open(struct pt_regs *ctx) {
	struct event data = {};
	if (!init_data(ctx, &data)) {
		return 0;
	}
	struct file *filp = (struct file *)PT_REGS_RC(ctx);
	struct path path = {};
	bpf_probe_read(&path, sizeof(path), &filp->f_path);
	struct dentry *dentry = path.dentry;

	struct vfsmount *mnt;// = path.mnt;
	struct super_block *sb;
	bpf_probe_read(&mnt, sizeof(struct vfsmount *), &path.mnt);
	bpf_probe_read(&sb, sizeof(struct super_block *), &mnt->mnt_sb);
	bpf_probe_read(&data.magic, sizeof(data.magic), &sb->s_magic);

	struct inode *inode;
	bpf_probe_read(&inode, sizeof(struct inode *), &dentry->d_inode);
	bpf_probe_read(&data.size, sizeof(data.size), &inode->i_size);

	struct qstr q = {};
	bpf_probe_read(&q, sizeof(q), &dentry->d_name);
	bpf_probe_read(data.path, MAX_PATH, q.name);
	if (data.path[0] == 0) { // ignore nil path
		return 0;
	}
	if (data.path[0] == '/' && data.path[1] == 0) { // ignore root path
		return 0;
	}
	if (data.path[0] != '/') {
		report_parent(ctx, &data, dentry);
	}

	submit_event(ctx, &data, dentry, EVENT_OPEN_FILE);
	return 0;
}

